package net.es.weathermap.metric;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Iterator;

import org.json.JSONObject;
import org.json.JSONException;

import net.es.weathermap.beans.Domain;
import net.es.weathermap.topo.network.NetworkInfo;

import org.apache.log4j.Logger;

public class ESnetMP extends UtilizationMP implements MetricProvider {

    private static Logger LOG = Logger.getLogger(ESnetMP.class);
    // current smallest resolution in seconds for metric server
    private static final long METRIC_RESOLUTION = 30;
    protected static ESnetMP instance;

    protected MetricCache getCache() {
        // determines whether need to fetch or not in getValues
        return null;
    }

    public static ESnetMP getInstance(Domain dom) {
        if (instance == null) {
            instance = new ESnetMP(dom);
        }
        return instance;
    }

    public static ESnetMP getInstance() {
        return instance;
    }

    private ESnetMP(Domain dom) {
        domain = dom;
        this.initNameMaps();
    }

    @SuppressWarnings("unchecked")
    public synchronized HashMap<String, CacheEntry>
        fetchValues(List<String> identifiers, Map<String, Object> options)
            throws JSONException {

        LOG.info("fetchValues.start");
        String metricType = "average";
        long now = System.currentTimeMillis();
        now /= 1000;
        int nowInt = (int) now;
        Integer startSeconds = 0;
        Integer endSeconds = 0;
        Integer aggrSeconds = 30;
        Integer timesteps = 1;
        int reqId = 0;
        if (options != null) {
            if (options.containsKey("metricType")) {
                metricType = (String) options.get("metricType");
            }
            if (options.containsKey("startSeconds")) {
                startSeconds = (Integer) options.get("startSeconds");
            }
            if (options.containsKey("endSeconds")) {
                endSeconds = (Integer) options.get("endSeconds");
            }
            if (options.containsKey("aggrSeconds")) {
                aggrSeconds = (Integer) options.get("aggrSeconds");
            }
            if (options.containsKey("timesteps")) {
                timesteps = (Integer) options.get("timesteps");
            }
        }
        if (startSeconds == 0) {
            startSeconds = new Integer(nowInt);
        }
        // need to go back roughly 8 minutes to get data from metrics server
        startSeconds -= 480;
        if (timesteps == 1) {
            // kludge for SC09 for demo mode
            if ((aggrSeconds == 120) && metricType.equals("max")) {
                NetworkInfo.getInstance().setReverse(true);
            } else {
                NetworkInfo.getInstance().setReverse(false);
            }
            if (aggrSeconds == 30) {
                endSeconds = startSeconds;
            } else {
                int modSeconds = startSeconds % aggrSeconds;
                startSeconds -= aggrSeconds + modSeconds;
                endSeconds = startSeconds + aggrSeconds;
            }
        } else {
            endSeconds -= 480;
        }
        LOG.info("metricType: " + metricType);
        LOG.info("startSeconds: " + startSeconds);
        LOG.info("endSeconds: " + endSeconds);
        LOG.info("aggrSeconds: " + aggrSeconds);
        HashMap<String, CacheEntry> fetchedValues =
            new HashMap<String, CacheEntry>();
        ArrayList<String> serverUrls = NetworkInfo.getInstance().getMetricServerUrls();
        ArrayList<String> newIds = new ArrayList<String>();
        ArrayList<JSONObject> reqs = new ArrayList<JSONObject>();
        HashMap<String, JSONObject> keyMapper = new HashMap<String,JSONObject>();
        for (String id : identifiers) {
            if (!newIds.contains(id)) {
                String guid = String.valueOf(now) + "." +
                              reqId;
                JSONObject obj = this.convertId(id, "in", metricType,
                                                startSeconds, endSeconds,
                                                aggrSeconds, guid, timesteps);
                reqs.add(obj);
                keyMapper.put(guid, obj);
                reqId++;
                guid = String.valueOf(now) + "." + reqId;
                obj = this.convertId(id, "out", metricType, startSeconds,
                                     endSeconds, aggrSeconds, guid, timesteps);
                reqs.add(obj);
                keyMapper.put(guid, obj);
                reqId++;
                newIds.add(id);
            }
        }
        if (reqs.isEmpty()) {
            LOG.info("fetchValues.finish (nothing to fetch)");
            return fetchedValues;
        } else {
            LOG.info("fetchValues: fetching "+reqs.size()+" values");
        }
        JSONObject response = null;
        response =
            HttpUtils.getInstance().getResponse("q", serverUrls, reqs);
        if (response == null) {
            LOG.info("fetchValues.finish (no response!)");
            return fetchedValues;
        }
        // for exceptions
        CacheEntry defaultVal = getDefaultVal();
        Iterator<String> it = (Iterator<String>) response.keys();
        while (it.hasNext()) {
            // key in response is unique integer id
            String key = it.next();
            // get key containing original request
            JSONObject req = keyMapper.get(key);
            try {
                this.putVal(req, fetchedValues, response.getJSONObject(key));
            } catch (JSONException ex) {
                LOG.info("caught exception: " + ex.getMessage());
                fetchedValues.put(getKey(req), defaultVal);
            }
        }
        LOG.info("fetchValues: fetched "+fetchedValues.size()+" values");
        return fetchedValues;
    }

    public CacheEntry getDefaultVal() {
        CacheEntry defaultVal = new CacheEntry();
        HashMap<String, ArrayList<Double>> entry = new
            HashMap<String, ArrayList<Double>>();
        ArrayList<Double> vals = new ArrayList<Double>();
        vals.add(0.0D);
        entry.put("in", vals);
        vals = new ArrayList<Double>();
        vals.add(0.0D);
        entry.put("out", vals);
        defaultVal.setEntry(entry);
        return defaultVal;
    }
}
